#include <iostream>
#include <queue>
#include <vector>
#include <cstring>
using namespace std;

// 定义迷宫的最大尺寸
const int MAXN = 305;

// 方向数组：上、下、左、右
int dx[] = {-1, 1, 0, 0};  // x轴方向变化
int dy[] = {0, 0, -1, 1};  // y轴方向变化

// 传送门结构体，用于存储传送门的两个端点坐标
struct Portal {
    int x1, y1; // 第一个端点的坐标
    int x2, y2; // 第二个端点的坐标
};

// 26个大写字母对应传送门（A-Z映射到0-25）
Portal portals[26];

// BFS节点结构，用于存储当前坐标和已走步数
struct Point {
    int x, y;    // 当前坐标
    int step;    // 已走步数
};

// 迷宫地图存储
char maze[MAXN][MAXN];

// 访问标记数组，用于记录哪些位置已经被访问过
bool visited[MAXN][MAXN];

// 迷宫的行数和列数
int n, m;

// 起点和终点的坐标
int startX, startY;
int endX, endY;

/**
 * 预处理函数：扫描迷宫并记录关键信息
 * 1. 记录起点和终点坐标
 * 2. 记录所有传送门的端点坐标
 */
void preprocess() {
    // 初始化传送门数组，将所有坐标设置为-1表示未记录
    memset(portals, -1, sizeof(portals));

    // 遍历迷宫的每一个格子
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            char c = maze[i][j];
            if (c == '@') { // 如果当前格子是起点
                startX = i;
                startY = j;
            } else if (c == '=') { // 如果当前格子是终点
                endX = i;
                endY = j;
            } else if (c >= 'A' && c <= 'Z') { // 如果当前格子是传送门
                int idx = c - 'A'; // 将字母转换为索引（A->0, B->1...）
                // 如果当前传送门的第一个端点未记录
                if (portals[idx].x1 == -1) {
                    portals[idx].x1 = i;
                    portals[idx].y1 = j;
                } else { // 记录第二个端点
                    portals[idx].x2 = i;
                    portals[idx].y2 = j;
                }
            }
        }
    }
}

/**
 * BFS核心函数：寻找从起点到终点的最短路径
 * @return 到达终点的最小步数（题目保证有解）
 */
int bfs() {
    queue<Point> q; // BFS队列，用于存储待处理的节点
    memset(visited, 0, sizeof(visited)); // 初始化访问标记数组

    // 将起点加入队列，并标记为已访问
    q.push({startX, startY, 0});
    visited[startX][startY] = true;

    // 开始BFS循环
    while (!q.empty()) {
        Point curr = q.front(); // 取出队列中的第一个节点
        q.pop();

        // 如果当前节点是终点，返回当前步数
        if (curr.x == endX && curr.y == endY)
            return curr.step;

        // 遍历四个方向
        for (int i = 0; i < 4; i++) {
            int nx = curr.x + dx[i]; // 计算新坐标
            int ny = curr.y + dy[i];

            // 边界检查：如果新坐标越界或撞墙，则跳过
            if (nx < 0 || nx >= n || ny < 0 || ny >= m) continue;
            if (maze[nx][ny] == '#') continue;

            /*----- 处理传送门机制 -----*/
            if (maze[nx][ny] >= 'A' && maze[nx][ny] <= 'Z') {
                int idx = maze[nx][ny] - 'A'; // 获取传送门索引
                Portal p = portals[idx];
                // 判断当前所在的是传送门的哪个端点
                if (nx == p.x1 && ny == p.y1) {
                    // 传送到第二个端点
                    nx = p.x2;
                    ny = p.y2;
                } else {
                    // 传送到第一个端点
                    nx = p.x1;
                    ny = p.y1;
                }
            }
            /*----- 传送处理结束 -----*/

            // 如果新位置未被访问过
            if (!visited[nx][ny]) {
                visited[nx][ny] = true; // 标记为已访问
                q.push({nx, ny, curr.step + 1}); // 加入队列，步数+1
            }
        }
    }
    return -1; // 理论上不会执行（题目保证有解）
}

int main() {
    // 读取输入：迷宫的行数和列数
    cin >> n >> m;

    // 读取迷宫地图
    for (int i = 0; i < n; i++)
        cin >> maze[i];

    // 预处理迷宫信息
    preprocess();

    // 执行BFS并输出结果
    cout << bfs() << endl;
    return 0;
}

/*
****** 算法细节说明 ******
1. 传送门处理机制：
   - 遇到传送门时强制传送，不消耗步数
   - 传送后的新坐标直接参与后续判断
   - 传送门可以重复使用（不标记访问状态）

2. BFS特性保证：
   - 队列先进先出特性保证最先到达终点的路径是最短的
   - visited数组防止重复访问普通节点

3. 预处理优化：
   - 提前记录所有传送门坐标，避免在BFS中重复扫描
   - 时间复杂度O(N*M)预处理 + O(N*M)BFS = O(N*M)

4. 特殊测试用例验证：
   - 起点紧邻传送门的情况
   - 传送门形成环路的情况
   - 最大尺寸迷宫测试（300x300）
*/